﻿//    TestGenerator - experiment with piping data into ffmpeg in "pipefmt"
//    Copyright (C) 2012  Juergen Bohl

//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.

//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.

//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace TestGenerator
{
    #region Using directives
    using System;
    using System.IO.Pipes;
    #endregion

    class FfmpegPipeBase : IDisposable
    {
        /// <summary>
        /// The name of the named pipe (just the name, not including \\.\pipe).
        /// </summary>
        private string pipeName;

        /// <summary>
        /// The named pipe instance.
        /// </summary>
        private NamedPipeServerStream serverStream;

        private bool isDisposed;

        /// <summary>
        /// Finalizes an instance of the <see cref="FfmpegVideoPipe"/> class.
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="FfmpegVideoPipe"/> is reclaimed by garbage collection.
        /// </summary>
        ~FfmpegPipeBase()
        {
            this.Dispose(false);
        }

        /// <summary>
        /// Gets the name of the pipe (without \\.\pipe\).
        /// </summary>
        /// <value>
        /// The name of the pipe.
        /// </value>
        public string PipeName
        {
            get { return this.pipeName; }
        }

        /// <summary>
        /// Gets the fully qualified name of the pipe (including \\.\pipe).
        /// </summary>
        /// <value>
        /// The name of the pipe file.
        /// </value>
        public string PipeFileName
        {
            get { return @"\\.\pipe\" + this.PipeName; }
        }

        protected void InitializePipe()
        {
            this.ThrowIfDisposed();
            this.pipeName = this.CreateUniquePipeName();
            this.serverStream = new NamedPipeServerStream(
                                                    this.PipeName,
                                                    PipeDirection.Out,
                                                    1,
                                                    PipeTransmissionMode.Byte,  /* what does it do or does it matter at all? */
                                                    PipeOptions.Asynchronous,
                                                    65535,                      /* what values should we choose here? */
                                                    65535);
        }

        public void WaitForConnection(int maxWaitTime)
        {
            this.WaitForConnectionCore(maxWaitTime);
        }

        /// <summary>
        /// Waits for the named pipe to be connected. The maximum time to wait is
        /// specified, after this time has elapsed a TimeOutException is thrown. If
        /// that is the case, the pipe is to be considered dead (and Initialize must
        /// be called again!).
        /// </summary>
        /// <param name="maxWaitTime">The maximum wait time in ms. If -1 we will wait forever.</param>
        protected virtual void WaitForConnectionCore(int maxWaitTime)
        {
            this.ThrowIfDisposed();
            this.CheckIsPipeOpened();

            if (maxWaitTime < 0)
            {
                this.serverStream.WaitForConnection();
            }
            else
            {
                IAsyncResult result = this.serverStream.BeginWaitForConnection(null, null);
                if (!result.AsyncWaitHandle.WaitOne(maxWaitTime))
                {
                    if (result.IsCompleted == false)
                    {
                        // this will unblock the hanging call
                        this.serverStream.Close();

                        // pipe is dead now...
                        this.serverStream = null;

                        // timeout...
                        throw new TimeoutException("NamedPipe did not connected within the timeout period.");
                    }
                }

                this.serverStream.EndWaitForConnection(result);
            }
        }


        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.isDisposed = true;
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Release managed resources:
                // Call Dispose() on other objects owned by this instance.
                if (this.serverStream != null)
                {
                    this.serverStream.Dispose();
                }
            }

            // Release unmanaged resources owned by (just) this instance.
        }

        protected void AddToPipe(byte[] data)
        {
            this.serverStream.Write(data, 0, data.Length);
        }

        public void Close()
        {
            this.ThrowIfDisposed();
            this.CheckIsPipeOpened();

            this.serverStream.Flush();
            this.Dispose();
        }

        /// <summary>
        /// Creates an unique name for the named pipe. We simply use a GUID.
        /// </summary>
        /// <returns>An unique name for the named pipe.</returns>
        protected virtual string CreateUniquePipeName()
        {
            Guid guid = Guid.NewGuid();
            return guid.ToString();
        }

        /// <summary>
        /// Checks if the pipe is opened (= if the class is initialized). Otherwise
        /// a InvalidOperationException is thrown.
        /// </summary>
        protected void CheckIsPipeOpened()
        {
            if (this.serverStream == null)
            {
                throw new InvalidOperationException("Class is not initialized!");
            }
        }

        protected void ThrowIfDisposed()
        {
            if (this.isDisposed == true)
            {
                throw new ObjectDisposedException("FfmpegVideoPipe");
            }
        }
    }
}
